package chartCraft.graphDesk;

import java.lang.Math;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.util.Observable;
import java.util.Observer;
import javax.swing.JLabel;

import chartCraft.model.LocationModel;
import chartCraft.model.Model.NotifyingMessage;
import chartCraft.model.TopModel;


public class ChartLabel extends JLabel implements Observer {
	private static final long serialVersionUID = 1L;
	
	private static final int BAR_GAP = 2;

	private TopModel mTopModel = null;
	private LocationModel mLocationModel = null;

	public ChartLabel(LocationModel chartModel, TopModel topModel) {
		super();
		setOpaque(false);

		setModel(chartModel, topModel);
	}

	private void setModel(LocationModel chartModel, TopModel topModel) {
		mLocationModel = chartModel;
		chartModel.addObserver(this);
		mTopModel = topModel;
		topModel.addObserver(this);
		
		repaint();
	}

	private double map(double inValue, double inLowerBound, double inUpperBound, double outLowerBound, double outUpperBound) {
		return (inValue - inLowerBound) / (inUpperBound - inLowerBound) * (outUpperBound - outLowerBound) + outLowerBound;
	}

	private Double getValueForRendering(int series) {
		if (mLocationModel.isPlaying()) {
			//animation
			final double frameNo = mLocationModel.getFrameNo();
			final double precedingMainFrameNo = Math.floor(frameNo);
			final double followingMainFrameNo = precedingMainFrameNo + 1.0;
			final double valueInPrecedingMainFrame = mLocationModel.getValueAt((int) precedingMainFrameNo, series);
			final double valueInFollowingMainFrame = mLocationModel.getValueAt((int) followingMainFrameNo, series);
			return map(frameNo, precedingMainFrameNo, followingMainFrameNo, 
					valueInPrecedingMainFrame, valueInFollowingMainFrame);

		} else {
			//normal plotting
			int time = mLocationModel.getTime();
			return mLocationModel.getValueAt(time, series);
		}
	}

	protected void paintComponent(Graphics g) {
		super.paintComponent(g); 
		g.setColor(Color.blue);
		int noOfSeries = mLocationModel.getNoOfSeries();
		int chartType = mLocationModel.getChartType();

		if (noOfSeries <= 0) {
			//nothing to do
			return;
		}

		switch(chartType) {
		case LocationModel.VERTICAL_BAR_CHART:
			renderVerticalBarChart(g);
			break;
		case LocationModel.HORIZONTAL_BAR_CHART:
			renderHorizontalBarChart(g);
			break;
		case LocationModel.HORIZONTAL_STACKED_BAR_CHART:
			renderHorizontalStackedBarChart(g);
			break;
		case LocationModel.PIE_CHART:
			renderPieChart(g);
			break;
		case LocationModel.VERTICAL_STACKED_BAR_CHART:
			renderVerticalStackedBarChart(g);
			break;
		}
	}

	private void renderPieChart(Graphics g) {
		Dimension size = getSize();
		int noOfSeries = mLocationModel.getNoOfSeries();
		
		double sumOfValue=0;
		for(int series = 0; series < noOfSeries; series++){
			sumOfValue+=getValueForRendering(series);
		}

		boolean nonZeroValueExist = false;
		for(int series = 0;series<noOfSeries; series++){
			if(getValueForRendering(series) != 0) {
				nonZeroValueExist = true;
			}
		}

		if(nonZeroValueExist) {
			int startArcAngle = 0;
			for(int series = 0;series<noOfSeries; series++){
				Color color = mTopModel.getColorForSeries(series);
				g.setColor(color);
				int arcAngle = (int) (getValueForRendering(series)/sumOfValue*360.0);
				g.fillArc(0, 0, size.width, size.height, startArcAngle, arcAngle);
				startArcAngle += arcAngle;
			}
		} else {
			g.setColor(Color.GRAY);
			g.drawArc(0, 0, size.width, size.height, 0, 360);
		}
	}

	private void renderVerticalStackedBarChart(Graphics g) {
		Dimension size = getSize();
		int noOfSeries = mLocationModel.getNoOfSeries();
	
		double totalSpan = mLocationModel.getMaxSumOfValueInSameLocation();;
	
		int currentBarYStartPosition = size.height - 1;
	
		for(int series = 0; series < noOfSeries; series++){
			Color color = mTopModel.getColorForSeries(series);
			Integer barHeight = (int) (getValueForRendering(series) /totalSpan * (size.height - 1));
			if (barHeight == 0) {
				barHeight = 1;
			}
			g.setColor(color);
			g.fillRect(0, currentBarYStartPosition - barHeight, size.width, barHeight);
			currentBarYStartPosition -= barHeight;
		}
	}

	private void renderHorizontalStackedBarChart(Graphics g) {
		Dimension size = getSize();
		int noOfSeries = mLocationModel.getNoOfSeries();

		double totalSpan = mLocationModel.getMaxSumOfValueInSameLocation();

		int currentBarXStartPosition = 0;

		for(int series = 0; series < noOfSeries; series++){
			Color color = mTopModel.getColorForSeries(series);
			Integer barWidth = (int) (getValueForRendering(series) /totalSpan * (size.width-1));
			if (barWidth == 0) {
				barWidth = 1;
			}
			g.setColor(color);
			g.fillRect(currentBarXStartPosition, 0, barWidth, size.height);
			currentBarXStartPosition += barWidth;
		}
	}

	private void renderVerticalBarChart(Graphics g) {
		Dimension size = getSize();
		int noOfSeries = mLocationModel.getNoOfSeries();

		double maxSpanValue = - mLocationModel.getLowerBoundOfValue() + mLocationModel.getUpperBoundOfValue();
		int zeroPos = size.height-1;
		if (maxSpanValue != 0) {
			zeroPos = (int) (mLocationModel.getUpperBoundOfValue() / maxSpanValue * (size.height -1));
		}
		final int totalBarGap = BAR_GAP * (noOfSeries - 1);
		int barWidth = (int)((size.width - totalBarGap) / noOfSeries);

		for (int series = 0; series < noOfSeries; series++) {
			Color color = mTopModel.getColorForSeries(series);
			g.setColor(color);
			double value = getValueForRendering(series);
			int xPos = series * (BAR_GAP + barWidth);
			
			int barHeight;
			int yPos;
			if (value > 0) {
				barHeight = (int) (value / maxSpanValue * size.height);
				yPos = zeroPos - barHeight;
			} else if (value == 0) {
				barHeight = 1;
				yPos = zeroPos;
			} else {
				barHeight = - (int) (value / maxSpanValue * size.height);
				yPos = zeroPos;
				
			}
			g.fillRect(xPos, yPos, barWidth, barHeight);
		}
	}

	private void renderHorizontalBarChart(Graphics g) {
		Dimension size = getSize();
		int noOfSeries = mLocationModel.getNoOfSeries();
	
		double maxSpanValue = - mLocationModel.getLowerBoundOfValue() + mLocationModel.getUpperBoundOfValue();
		int zeroPos = 0;
		if (maxSpanValue != 0) {
			zeroPos = (int) (-mLocationModel.getLowerBoundOfValue() / maxSpanValue * (size.width-1));
		}
		final int totalBarGap = BAR_GAP * (noOfSeries - 1);
		int barHeight = (int)((size.height - totalBarGap) / noOfSeries);
	
		for (int series = 0; series < noOfSeries; series++) {
			Color color = mTopModel.getColorForSeries(series);
			g.setColor(color);
			double value = getValueForRendering(series);
			int yPos = series * (BAR_GAP + barHeight);
			
			int barWidth;
			int xPos;
			if (value > 0) {
				barWidth = (int) (value / maxSpanValue * size.width);
				xPos = zeroPos;
			} else if (value == 0) {
				barWidth = 1;
				xPos = zeroPos;
			} else {
				barWidth = - (int) (value / maxSpanValue * size.width);
				xPos = zeroPos - barWidth;
				
			}
			g.fillRect(xPos, yPos, barWidth, barHeight);
	
		}
	}

	@Override
	public void update(Observable o, Object arg) {
		NotifyingMessage msg = (NotifyingMessage) arg;
		String action = msg.getAction();

		if (o == mLocationModel) {
			if (action == LocationModel.DATA_CHANGE) {
				repaint();

			} else if (action == LocationModel.NAME_CHANGE) {
				//Do nothing

			} else if (action == LocationModel.STUCTURE_CHANGE) {
				repaint();

			} else if (action == LocationModel.LOCATION_TIME_UPDATED) {
				repaint();

			} else if (action == LocationModel.BOUND_CHANGED) {
				repaint();

			} else if (action == LocationModel.PLAY_STATUS_CHANGED) {
				repaint();

			} else if (action == LocationModel.PLAY_FRAME_NO_CHANGED) {
				repaint();

			} else if (action == LocationModel.CHART_TYPE_CHANGED) {
				repaint();

			}
		}
		
		if (o == mTopModel) {
			if (action == TopModel.SERIES_COLOR_CHANGED) {
				repaint();
			} 
		}
	}

}
